package osami.communication;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import osami.communication.translate.FindServiceMessage;
import osami.communication.translate.Message;
import osami.communication.translate.RemoteCallMessage;
import osami.communication.translate.ServiceUpdateMessage;
import osami.communication.translate.TranslationException;
//import osami.engine.OsamiEngine;
//import osami.engine.OsamiException;


/**
 * TCPIPConnection handles transferring data over the connection.
 * 
 * @author osami
 *
 */
public class TCPIPConnection extends Connection
{
	private Socket iSocket = null;
	private BufferedReader iInStream = null;
	private PrintWriter iOutStream = null;
	private Listener iListener = null;
	private Thread iListenerThread = null;
	
	
	/**
	 * Creates a new instance of TCPIPConnection.
	 * 
	 * @param aSocket
	 * @param aEngine
	 * @param aCommServer
	 * @throws IOException
	 */
	public TCPIPConnection( Activator aEngine, CommServer aCommServer, Socket aSocket ) throws IOException
	{
		super( aEngine, aCommServer, new TCPIPAddress( aSocket.getInetAddress().getHostAddress() ) );

		iSocket = aSocket;

		iInStream = new BufferedReader( new InputStreamReader( aSocket.getInputStream() ) );
		iOutStream = new PrintWriter( aSocket.getOutputStream(), true );

		// set translator initially to json
		try
		{
			setTranslator( iCommServer.getTranslator( "json" ) );
		}
		catch ( OsamiException aException )
		{
			aException.printStackTrace();
		}
		
		// create a new listener and create a thread for it 
		iListener = new Listener( this );
		iListenerThread = new Thread( iListener );
		iListenerThread.start();
	}
	
	/**
	 * Closes the client socket and removes itself
	 * from communication server's client vector.
	 * 
	 */
	public void unregister()
	{
		if ( iIdentifier != null )
			System.out.println( "TCPIPConnection.unregister: Unregistering connection to node " + iIdentifier.getID() + "." );
		else
			System.out.println( "TCPIPConnection.unregister: Unregistering connection to uninitialized node." );

		// close the socket
		try
		{
			if ( iSocket != null )
			{
				iSocket.close();
				iSocket = null;
			}
		}
		catch ( IOException aException )
		{
			aException.printStackTrace();
		}

		// kill the listener thread
		iListenerThread.interrupt();
		
		// remove us from the communication server
		iCommServer.removeClient( this );
	}
	
	/**
	 * Listener-class listens to inStream and constructs
	 * JSON objects from the received data.
	 * 
	 * @author osami
	 *
	 */
	private class Listener implements Runnable
	{
		private TCPIPConnection iConnection = null;
		
		public Listener( TCPIPConnection aConnection )
		{
			iConnection = aConnection;
		}
		
		
		public void run()
		{
			System.out.println( "TCPIPConnection.Listener.run: started." );
			

			// handshake
			try
			{
				System.out.println( "TCPIPConnection.Listener.run: waiting for the handshake..." );
				iIdentifier = iCommServer.handleHandshake( iInStream.readLine(), iConnection, true );
				System.out.println( "TCPIPConnection.Listener.run: handshake received!" );
			}
			catch ( IOException aException )
			{
				System.out.println( "TCPIPConnection.Listener.run: Connection was lost or inStream failed to read:" );
				aException.printStackTrace();
			}
			catch ( TranslationException aException )
			{
				System.out.println( "TCPIPConnection.Listener.run: Translation exception caught: " + aException.getMessage() );
			}
			catch ( OsamiException aException )
			{
				System.out.println( "TCPIPConnection.Listener.run: OSAMI exception: " + aException.getMessage() );
				aException.printStackTrace();
			}

			
			// send a service update message to the other node
			try
			{
				send( iEngine.getServicesMessage() );
			}
			catch ( TranslationException aException )
			{
				aException.printStackTrace();
			}


			// start listening to the "other side's" sender object
			System.out.println( "TCPIPConnection.Listener.run: handshake protocol over, starting to listen to incoming calls." );
			String inString = "";
			try
			{
				while( iSocket.isConnected() && inString != null )
				{
					if ( ( inString = iInStream.readLine() ) != null )
					{
						System.out.println( "TCPIPConnection.Listener.run: Got a message: " + inString );
						Message message = iTranslator.translate( inString );

						switch( message.getType() )
						{
						case Message.REMOTE_CALL:
						{
							remoteCall( (RemoteCallMessage)message );
						}break;
						case Message.SERVICE_UPDATE:
						{
							iEngine.handleOsamiServiceUpdate( (ServiceUpdateMessage)message );
						}break;
						case Message.FIND_SERVICE:
						{
							iEngine.handleFindServiceMessage( (FindServiceMessage)message );
						}break;
						default: iStore.putObject( message );
						}
					}
				}
			}
			catch ( IOException aException )
			{
				System.out.println( "TCPIPConnection.run: " + aException.getMessage() );
			}
			catch ( TranslationException aException )
			{
				aException.printStackTrace();
			}
			catch ( OsamiException aException )
			{
				aException.printStackTrace();
			}
			
			
			// problems with the socket -> disconnect
			System.out.println( "TCPIPConnection.Listener.run: stopping." );

			// unregister connection
			unregister();
		}
	}

	/**
	 * Writes a message to the outStream. If the message is a request,
	 * the call blocks until a response is received.
	 * 
	 * @param aMessage
	 * @return Message
	 * @throws TranslationException 
	 */
	public Message send( Message aMessage ) throws TranslationException
	{
		System.out.println( "TCPIPConnection.send: sending message: " + aMessage.getClass().getName() );

		// translate the message
		String translated = iTranslator.translate( aMessage );
		
		// if message is a request, we will wait for an answer
		if ( aMessage.isRequest() )
		{
			// send the message
			iOutStream.println( translated );

			// wait for response
			return iStore.getObject( aMessage.getCallNumber(), calculateTimeout( aMessage ) );
		}
		else
		{
			// send the message
			iOutStream.println( translated );
		}

		return null;
	}
	
	/**
	 * Passes a remote call message to OSAMI main to be handled.
	 * 
	 * @param aMessage
	 * @throws TranslationException 
	 * @throws OsamiException 
	 */
	protected void remoteCall( RemoteCallMessage aMessage ) throws TranslationException, OsamiException
	{
		send( iEngine.handleBundleCall( aMessage ) );
	}

	@Override
	public void closeConnection()
	{
		unregister();
		
		try
		{
			if ( iInStream != null )
				iInStream.close();
			
			if ( iOutStream != null )
				iOutStream.close();
			
			if ( iSocket != null )
				iSocket.close();
		}
		catch ( IOException aException )
		{
			aException.printStackTrace();
		}
	}
}
